
/* GCSx
** GRAPHICS.CPP
**
** Basic graphics primitives/support via SDL
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

SDL_Color phaseRGB[NUM_PHASES] = {
    { 0, 0, 0 },
    { 64, 64, 64 },
    { 128, 128, 128 },
    { 192, 192, 192 },
    { 255, 255, 255 },
    { 192, 192, 192 },
    { 128, 128, 128 },
    { 64, 64, 64 },
    };

Uint32 phaseColors[NUM_PHASES];

SDL_Color guiRGB[COLOR_COUNT] = {
    { 224, 224, 224 }, // COLOR_FILL - GUI fill
    { 255, 255, 255 }, // COLOR_LIGHT1 - Light bevel on GUI
    { 224, 224, 224 }, // COLOR_LIGHT2
    { 112, 112, 112 }, // COLOR_DARK1 - Dark bevel on GUI
    { 168, 168, 168 }, // COLOR_DARK2
    { 204, 204, 204 }, // COLOR_BUTTONFILL1 - Normal button gradient
    { 244, 244, 244 }, // COLOR_BUTTONFILL2
    { 176, 176, 176 }, // COLOR_BUTTONDOWN1 - Depressed button gradient
    { 216, 216, 216 }, // COLOR_BUTTONDOWN2
    { 0, 0, 0 },       // COLOR_TEXT - GUI text
    { 176, 176, 255 }, // COLOR_TITLEACTIVE1 - Active titlebar gradient
    { 255, 176, 255 }, // COLOR_TITLEACTIVE2
    { 112, 112, 152 }, // COLOR_TITLEINACTIVE1 - Inactive titlebar gradient
    { 152, 112, 152 }, // COLOR_TITLEINACTIVE2
    { 255, 255, 255 }, // COLOR_TEXTBOX - Textboxes fill
    { 0, 0, 0 },       // COLOR_CURSOR - Cursor in textboxes
    { 0, 0, 255 },     // COLOR_SELECTION1 - Selected text fill gradient
    { 128, 0, 128 },   // COLOR_SELECTION2
    { 255, 255, 128 }, // COLOR_TILECURSOR - Cursor when selecting tiles/sprites
    { 128, 128, 255 }, // COLOR_TILESELECTION - Overlaid over selected tiles/sprites
    { 255, 255, 255 }, // COLOR_TILECURSORBORDER1 - Fade range for glowing portion of tile cursor
    { 0, 0, 0 },       // COLOR_TILECURSORBORDER2
    { 160, 160, 160 }, // COLOR_SCROLLTRACK - Fill for scrollbar gutter
    { 0, 0, 0 },       // COLOR_LINEBORDER - Thin border on GUI widgets that don't use a bevel
    { 0, 0, 0 },       // COLOR_BKFILL - Fill/background for editing windows/areas
    { 64, 64, 192 },   // COLOR_GRID - Grid lines
    { 11, 11, 11 },    // COLOR_FRONTDESKTOP - Desktop fill for frontend
    { 0, 0, 26 },      // COLOR_EDITORDESKTOP - Desktop fill for editor
    { 0, 0, 0 },       // COLOR_TRANSPARENT1 - Used to show areas that are 100% transparent
    { 128, 128, 128 }, // COLOR_TRANSPARENT2
    { 255, 255, 192 }, // COLOR_TOOLTIP - Tooltip fill
    { 255, 0, 0 },     // COLOR_GLYPHWIDTH - Glyph width marker
    { 0, 0, 255 },     // COLOR_POPUPFILL1 - Selected menu item gradient
    { 128, 0, 128 },   // COLOR_POPUPFILL2
    { 255, 255, 255 }, // COLOR_POPUPTEXT - Selected menu item text
    { 128, 128, 128 }, // COLOR_CONSOLEPROMPT - Console prompt
    { 192, 0, 0 },     // COLOR_CONSOLEINPUT - User input in console
    };
    
Uint32 guiPacked[COLOR_COUNT];

SDL_Surface* actualScreen = NULL;
// In normal mode, points to actual
// In OpenGL mode, points to allocated 32bit surface
SDL_Surface* proxyScreen = NULL;
TextureMap* textureScreen = NULL;
TextureMap* textureMice = NULL;
int proxyScreenAllocated = 0;
int glScreenState = 0;
GLfloat glAlpha = 1.0f;

int requestedX = 0;
int requestedY = 0;
int requestedBits = 0;
int requestedFullscreen = 0;
int requestedResizable = 0;
int requestedGL = 0;

int screenWidth = 0;
int screenHeight = 0;

int actualX = 0;
int actualY = 0;
int actualBits = 0; // bits of screen surface we have
int hardwareBits = 0; // bits of the hardware itself currently
int defaultBits = 0; // bits we will always use when windowed

// (previous actual)
int previousX = 0;
int previousY = 0;
int previousBits = 0;
int previousFullscreen = 0;
int previousResizable = 0;
int previousGL = 0;

MousePointer* micePointers[MOUSE_COUNT] = { NULL, NULL, NULL, NULL, NULL, NULL };
const char* micePointerFiles[MOUSE_COUNT] = {
    "mousenorm.bmp",
    "mousevert.bmp",
    "mousehoriz.bmp",
    "mousebeam.bmp",
    "mousediagdown.bmp",
    "mousediagup.bmp",
    "mousepixel.bmp",
    "mouseadd.bmp",
    "mousesub.bmp",
    "mousefour.bmp"
    };
int micePointerSpots[MOUSE_COUNT][2] = { { 0, 0 }, { 5, 8 }, { 8, 5 },
                                         { 3, 7 }, { 6, 6 }, { 6, 6 },
                                         { 5, 5 }, { 5, 5 }, { 5, 5 }, 
                                         { 10, 10 } };
int currentMouse = 0;

// Largest mouse size, and storage to unblit it from
int mouseXSize = 0;
int mouseYSize = 0;
SDL_Surface* mouseUnblit = NULL;
int lastMouseX = 0;
int lastMouseY = 0;
int mouseBlitted = -1;

// Used for screenFlip- first rectangle is main screen
// area to update; second and third are the last mouse
// unblit and blit areas
SDL_Rect updateRects[3];
int numUpdate = 1;

Uint32 convertRGB(SDL_Color color, int alpha) { start_func
    assert(proxyScreen);

    if (alpha >= 0) return SDL_MapRGBA(proxyScreen->format, color.r, color.g, color.b, alpha);
    return SDL_MapRGB(proxyScreen->format, color.r, color.g, color.b);
}

// Call whenever palette/mode changes
void convertGuiItems() { start_func
    assert(proxyScreen);

    int pos;
    
    for (pos = 0; pos < COLOR_COUNT; ++pos) {
        guiPacked[pos] = convertRGB(guiRGB[pos]);
    }

    for (pos = 0; pos < MOUSE_COUNT; ++pos) {
        micePointers[pos]->convertFor(proxyScreen);
        if (requestedGL)
            micePointers[pos]->convertGL(textureMice, pos + 1, mouseXSize, mouseYSize);
    }
    
    for (pos = 0; pos < NUM_PHASES; ++pos) {
        phaseColors[pos] = convertRGB(phaseRGB[pos]);
    }
    
    // Storage for unblitting mouse
    if (mouseUnblit) SDL_FreeSurface(mouseUnblit);
    mouseUnblit = NULL;
    mouseUnblit = createSurface(mouseXSize, mouseYSize);
    mouseBlitted = -1;
}

void initVideo() { start_func
    const SDL_VideoInfo* videoInfo;
    
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE) < 0) {
        fatalCrash(0, "Unable to init SDL: %s", SDL_GetError());
    }

    videoInfo = SDL_GetVideoInfo();
    defaultBits = videoInfo->vfmt->BitsPerPixel;

    if (defaultBits < 15) {
        fatalCrash(0, "Full-color video not supported");
    }

    string mouseFile;

    for (int pos = 0; pos < MOUSE_COUNT; ++pos) {
        if (micePointers[pos] == NULL) {
            createFilename(resourceDir->c_str(), micePointerFiles[pos], mouseFile);
            micePointers[pos] = new MousePointer(mouseFile.c_str(), micePointerSpots[pos][0], micePointerSpots[pos][1]);
            if (micePointers[pos]->xSize() > mouseXSize) mouseXSize = micePointers[pos]->xSize();
            if (micePointers[pos]->ySize() > mouseYSize) mouseYSize = micePointers[pos]->ySize();
        }
    }
    
    assert(mouseXSize > 0);
    assert(mouseYSize > 0);

    // We don't really care about inability to load our icon
    string iconFile;
    createFilename(resourceDir->c_str(), FILENAME_ICON, iconFile);

    SDL_Surface* icon = SDL_LoadBMP(iconFile.c_str());
    if (icon) SDL_WM_SetIcon(icon, NULL);
    SDL_FreeSurface(icon);
}

void selectVideoMode(int x, int y, int bpp, int fullscreen, int resizable, int gl) throw_Video { start_func
    const SDL_VideoInfo* videoInfo;
    
    if (bpp == -1) bpp = actualBits ? actualBits : defaultBits;
    if (x == -1) x = actualX;
    if (y == -1) y = actualY;
    if (fullscreen == -1) fullscreen = requestedFullscreen;
    if (resizable == -1) resizable = requestedResizable;
    if (gl == -1) gl = requestedGL;

    assert(x >= 0);
    assert(y >= 0);
    assert((bpp == 15) || (bpp == 16) || (bpp == 24) || (bpp == 32));
    
    // Certain minimum size
    if (x < 320) x = 320;
    if (y < 240) y = 240;
    
    // If fullscreen, not resizable
    if (fullscreen) {
        resizable = 0;
    }
    
    // If windowed, always go with default hardware bpp
    else {
        bpp = defaultBits;
    }

    previousX = actualX;
    previousY = actualY;
    previousBits = actualBits;
    previousFullscreen = requestedFullscreen;
    previousResizable = requestedResizable;
    previousGL = requestedGL;

    if (gl) {
        // @TODO: optimal settings here? should be based on bpp?
        SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
        SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
        SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
        SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
        SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
        SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    }

    if (proxyScreenAllocated) {
        SDL_FreeSurface(proxyScreen);
        proxyScreenAllocated = 0;
        delete textureScreen;
        textureScreen = NULL;
        delete textureMice;
        textureMice = NULL;
    }
    // IMPORTANT: If you change this from SDL_SWSURFACE, you may need to add in surface locking!
    actualScreen = SDL_SetVideoMode(x, y, bpp,
                                    (gl ? SDL_OPENGL : SDL_SWSURFACE) |
                                    (fullscreen ? SDL_FULLSCREEN : 0) |
                                    (resizable ? SDL_RESIZABLE : 0)
                                    );

    if (actualScreen == NULL) {
        if (previousX) {
            // IMPORTANT: If you change this from SDL_SWSURFACE, you may need to add in surface locking!
            actualScreen = SDL_SetVideoMode(previousX, previousY, previousBits,
                                            (previousGL ? SDL_OPENGL : SDL_SWSURFACE) |
                                            (previousFullscreen ? SDL_FULLSCREEN : 0) |
                                            (previousResizable ? SDL_RESIZABLE : 0)
                                           );
            if ((previousGL) && (actualScreen)) {
                proxyScreen = createSurface32(actualScreen->w, actualScreen->h);
                proxyScreenAllocated = 1;
                textureScreen = new TextureMap(1, actualScreen->w, actualScreen->h, NULL);
                textureMice = new TextureMap(MOUSE_COUNT, mouseXSize, mouseYSize, NULL);
                glScreenState = 0;
                glAlpha = 1.0f;
            }
            else proxyScreen = actualScreen;
    
            if (actualScreen == NULL) {
                fatalCrash(0, "Unable to set %dx%dx%d video: %s", x, y, bpp, SDL_GetError());
            }
            else {
                throw VideoException("Unable to set %dx%dx%d video: %s", x, y, bpp, SDL_GetError());
            }
        }
        else {
            // Only fatal if no mode to fall back on
            fatalCrash(0, "Unable to set %dx%dx%d video: %s", x, y, bpp, SDL_GetError());
        }
    }
    else {
        if (fullscreen) {
            debugWrite(DEBUG_VIDEO, "Video mode %dx%dx%d", x, y, bpp);
        }
        else {
            debugWrite(DEBUG_VIDEO, "Video mode %dx%dx%d (w)", x, y, bpp);
        }
    }
    
    if (gl) {
        proxyScreen = createSurface32(actualScreen->w, actualScreen->h);
        proxyScreenAllocated = 1;
        textureScreen = new TextureMap(1, actualScreen->w, actualScreen->h, NULL);
        textureMice = new TextureMap(MOUSE_COUNT, mouseXSize, mouseYSize, NULL);
        glScreenState = 0;
        glAlpha = 1.0f;
    }
    else proxyScreen = actualScreen;
    
    requestedX = x;
    requestedY = y;
    requestedBits = bpp;
    requestedFullscreen = fullscreen;
    requestedResizable = resizable;
    requestedGL = gl;

    videoInfo = SDL_GetVideoInfo();
    hardwareBits = videoInfo->vfmt->BitsPerPixel;
    actualBits = actualScreen->format->BitsPerPixel;
    screenWidth = actualX = proxyScreen->w;
    screenHeight = actualY = proxyScreen->h;
    
    if ((x != actualX) || (y != actualY) || (bpp != actualBits)) {
        debugWrite(DEBUG_VIDEO, "Actual mode used: %dx%dx%d", actualX, actualY, actualBits);
    }
    if (hardwareBits != actualBits) {
        debugWrite(DEBUG_VIDEO, "Hardware BPP: %d", hardwareBits);
    }
    
    SDL_SetAlpha(proxyScreen, 0, 255);
    SDL_ShowCursor(SDL_DISABLE);
       
    convertGuiItems();

    // Resize desktop and all windows on it
    if (desktop != NULL) {
        desktop->resolutionChange(previousX, previousY, previousBits, actualX, actualY, actualBits);
    }

    // Save config (gl mode is game mode and doesn't count for configuration)
    if (!gl) {
        config->write(VIDEO_X, actualX);
        config->write(VIDEO_Y, actualY);
        config->write(VIDEO_BPP, actualBits);
        config->write(VIDEO_FULLSCREEN, requestedFullscreen);
        config->write(VIDEO_RESIZABLE, requestedResizable);
    }
    
    // Initialize OpenGL?
    if (gl) {
        oglInit(actualX, actualY);
    }
}

void exitVideo() { start_func
    for (int pos = 0; pos < MOUSE_COUNT; ++pos) {
        delete micePointers[pos];
        micePointers[pos] = NULL;
    }
    if (proxyScreenAllocated) {
        SDL_FreeSurface(proxyScreen);
        proxyScreenAllocated = 0;
        delete textureScreen;
        textureScreen = NULL;
        delete textureMice;
        textureMice = NULL;
    }
}

int screenBits() { start_func
    return actualBits;
}

int screenFullscreen() { start_func
    return requestedFullscreen;
}

int screenResizable() { start_func
    return requestedResizable;
}

int screenGL() { start_func
    return requestedGL;
}

void drawPixel(int x, int y, SDL_Color color, SDL_Surface* surface) { start_func
    drawPixel(x, y, convertRGB(color), surface);
}

void drawPixel(int x, int y, Uint32 data, SDL_Surface* surface) { start_func
    assert(surface);

    Uint16* bufp16;
    Uint32* bufp32;
    SDL_Rect clip;

    SDL_GetClipRect(surface, &clip);
    if ((x < clip.x) || (y < clip.y) || (x >= clip.x + clip.w) || (y >= clip.y + clip.h)) return;

    switch (surface->format->BytesPerPixel) {
        case 2:
            bufp16 = (Uint16*)surface->pixels + y*surface->pitch/2 + x;
            *bufp16 = data;
            break;
        case 4:
            bufp32 = (Uint32*)surface->pixels + y*surface->pitch/4 + x;
            *bufp32 = data;
            break;
    }
}

Uint32 getPixel(int x, int y, SDL_Surface* surface) { start_func
    assert(surface);

    Uint16* bufp16;
    Uint32* bufp32;
    SDL_Rect clip;

    SDL_GetClipRect(surface, &clip);
    if ((x < clip.x) || (y < clip.y) || (x >= clip.x + clip.w) || (y >= clip.y + clip.h)) return 0;

    switch (surface->format->BytesPerPixel) {
        case 2:
            bufp16 = (Uint16*)surface->pixels + y*surface->pitch/2 + x;
            return *bufp16;
        case 4:
            bufp32 = (Uint32*)surface->pixels + y*surface->pitch/4 + x;
            return *bufp32;
    }
    
    return 0;
}

void alphaBlit32(int sX, int sY, const SDL_Surface* src, int dX, int dY, SDL_Surface* dest, int width, int height) { start_func
    assert(src);
    assert(dest);
    
    width = (width == -1 ? src->w : width);
    height = (height == -1 ? src->h : height);

    Uint32 Amask = src->format->Amask;
    Uint32 Ashift = src->format->Ashift;
    
    for (int x = 0; x < width; ++x) {
        for (int y = 0; y < height; ++y) {
            Uint32 color = *((Uint32*)src->pixels + (sY + y)*src->pitch/4 + sX + x);
            Uint8 alpha = (color & Amask) >> Ashift;
            // Color becomes at 255 alpha, use alpha for blending instead
            _PutPixelAlpha(dest, dX + x, dY + y, color | Amask, alpha);
        }
    }
}

void alphaBlitFx(int sX, int sY, const SDL_Surface* src, int dX, int dY, SDL_Surface* dest, int width, int height, int mirror, int flip, int rotate90, int red, int green, int blue, int rgbScale, int alpha, int alphaScale) {
    assert(src);
    assert(dest);

    if (alpha <= 0) return;
    
    // Default w/h
    width = (width == -1 ? src->w : width);
    height = (height == -1 ? src->h : height);
    
    // Determine target rect, accounting for clip
    Rect target = { dX, dY, width, height };
    SDL_Rect Sclip;
    SDL_GetClipRect(dest, &Sclip);
    Rect clip = { Sclip.x, Sclip.y, Sclip.w, Sclip.h };
    if (!intersectRects(target, clip)) return;

    // Adjust source position
    sX += target.x - dX;
    sY += target.y - dY;
    
    // Determine increment, accounting for flip/mirror
    int srcPitch = src->pitch / 4 - target.w;
    int srcAdd = 1;
    if (rotate90) {
        assert(width == height);
        
        srcAdd = src->pitch / -4;
        srcPitch = -srcAdd * target.h + 1;
        if (mirror) {
            srcAdd = -srcAdd;
            srcPitch = -srcAdd * target.h + 1;
        }
        else {
            sY = sY + target.h - 1;
        }
        if (flip) {
            sX = sX + target.w - 1;
            srcPitch -= 2;
        }
    }
    else {
        if (flip) {
            sY = sY + target.h - 1;
            srcPitch -= src->pitch / 2;
        }
        if (mirror) {
            sX = sX + target.w - 1;
            srcAdd = -1;
            srcPitch += target.w * 2;
        }
    }
    Uint32* srcP = (Uint32*)src->pixels + sX + sY * src->pitch / 4;

    // Destination range
    int odX = target.x;
    dY = target.y;
    int dX2 = odX + target.w;
    int dY2 = dY + target.h;

    // Bitmasks/shifts
    Uint32 rdmask = dest->format->Rmask;
    Uint32 gdmask = dest->format->Gmask;
    Uint32 bdmask = dest->format->Bmask;

    int runshift = dest->format->Rshift;
    int gunshift = dest->format->Gshift;
    int bunshift = dest->format->Bshift;
    
    // Optimize for no color/no alpha
    int applyColor = 0;
    int applyAlpha = 0;
    if (red < rgbScale) applyColor = 1;
    if (green < rgbScale) applyColor = 1;
    if (blue < rgbScale) applyColor = 1;
    if (alpha < alphaScale) applyAlpha = 1;

    switch (dest->format->BytesPerPixel) {
        case 2: {
            Uint16* destP = (Uint16*)dest->pixels + odX + dY * dest->pitch / 2;
            int destPitch = dest->pitch / 2 - target.w;

            int rloss = dest->format->Rloss;
            int gloss = dest->format->Gloss;
            int bloss = dest->format->Bloss;

            for (; dY < dY2; ++dY) {
                for (dX = odX; dX < dX2; ++dX) {
                    // Pixel
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
                    int r = (*srcP & 0xff000000) >> 24;
                    int g = (*srcP & 0x00ff0000) >> 16;
                    int b = (*srcP & 0x0000ff00) >> 8;
                    Uint32 a = *srcP & 0x000000ff;
#else
                    int r = *srcP & 0x000000ff;
                    int g = (*srcP & 0x0000ff00) >> 8;
                    int b = (*srcP & 0x00ff0000) >> 16;
                    Uint32 a = *srcP & 0xff000000;
#endif

                    // Apply color
                    if (applyColor) {
                        r = r * red / rgbScale;
                        g = g * green / rgbScale;
                        b = b * blue / rgbScale;
                    }

                    // Apply alpha
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
                    if ((a != 0x000000ff) || (applyAlpha)) {
#else
                    if ((a != 0xff000000) || (applyAlpha)) {
                        a = a >> 24;
#endif
                        a = a * alpha / alphaScale;
                        a = a ^ 0xFF; // (flipped)

                        r += (((int)((*destP & rdmask) >> runshift) - r) * a) >> 8;
                        g += (((int)((*destP & gdmask) >> gunshift) - g) * a) >> 8;
                        b += (((int)((*destP & bdmask) >> bunshift) - b) * a) >> 8;
                    }
                    
                    // Scale down
                    r >>= rloss;
                    g >>= gloss;
                    b >>= bloss;

                    // Place
                    *destP = (r << runshift) | (g << gunshift) | (b << bunshift);

                    ++destP;
                    srcP += srcAdd;
                }
                destP += destPitch;
                srcP += srcPitch;
            }

            break;
        }

        case 4: {
            Uint32* destP = (Uint32*)dest->pixels + odX + dY * dest->pitch / 4;
            int destPitch = dest->pitch / 4 - target.w;

            for (; dY < dY2; ++dY) {
                for (dX = odX; dX < dX2; ++dX) {
                    // Pixel
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
                    int r = (*srcP & 0xff000000) >> 24;
                    int g = (*srcP & 0x00ff0000) >> 16;
                    int b = (*srcP & 0x0000ff00) >> 8;
                    Uint32 a = *srcP & 0x000000ff;
#else
                    int r = *srcP & 0x000000ff;
                    int g = (*srcP & 0x0000ff00) >> 8;
                    int b = (*srcP & 0x00ff0000) >> 16;
                    Uint32 a = *srcP & 0xff000000;
#endif

                    // Apply color
                    if (applyColor) {
                        r = r * red / rgbScale;
                        g = g * green / rgbScale;
                        b = b * blue / rgbScale;
                    }

                    // Apply alpha
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
                    if ((a != 0x000000ff) || (applyAlpha)) {
#else
                    if ((a != 0xff000000) || (applyAlpha)) {
                        a = a >> 24;
#endif
                        a = a * alpha / alphaScale;
                        a = a ^ 0xFF; // (flipped)

                        r += (((int)((*destP & rdmask) >> runshift) - r) * a) >> 8;
                        g += (((int)((*destP & gdmask) >> gunshift) - g) * a) >> 8;
                        b += (((int)((*destP & bdmask) >> bunshift) - b) * a) >> 8;
                    }

                    // Place
                    *destP = (r << runshift) | (g << gunshift) | (b << bunshift);

                    ++destP;
                    srcP += srcAdd;
                }
                destP += destPitch;
                srcP += srcPitch;
            }

            break;
        }
    }
}

void maskBlit32(int sX, int sY, SDL_Surface* src, int dX, int dY, SDL_Surface* dest, int mX, int mY, const SDL_Surface* mask, int width, int height, int clear, Uint32 clearColor) { start_func
    assert(src);
    assert(dest);
    assert(mask);
    if (src == dest) {
        assert(sX == dX);
        assert(sY == dY);
    }
    if (src == mask) {
        assert(sX == mX);
        assert(sY == mY);
    }
    if (dest == mask) {
        assert(dX == mX);
        assert(dY == mY);
    }

    width = (width == -1 ? src->w : width);
    height = (height == -1 ? src->h : height);

    Uint32 Amask = mask->format->Amask;
    
    Uint32* srcData = (Uint32*)src->pixels + sY*src->pitch/4 + sX;
    Uint32* destData = (Uint32*)dest->pixels + dY*dest->pitch/4 + dX;
    Uint32* maskData = (Uint32*)mask->pixels + mY*mask->pitch/4 + mX;
    int srcPitch = src->pitch/4 - width;
    int destPitch = dest->pitch/4 - width;
    int maskPitch = mask->pitch/4 - width;

    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            if (*maskData & Amask) {
                *destData = *srcData;
                if (clear) *srcData = clearColor;
            }
        
            ++srcData;
            ++destData;
            ++maskData;
        }
        srcData += srcPitch;
        destData += destPitch;
        maskData += maskPitch;
    }
}

void maskAlphaBlit32(int sX, int sY, SDL_Surface* src, int dX, int dY, SDL_Surface* dest, int mX, int mY, const SDL_Surface* mask, int alphaPercent, int width, int height, int clear, Uint32 clearColor) { start_func
    assert(src);
    assert(dest);
    assert(mask);
    if (src == dest) {
        assert(sX == dX);
        assert(sY == dY);
    }
    if (src == mask) {
        assert(sX == mX);
        assert(sY == mY);
    }
    if (dest == mask) {
        assert(dX == mX);
        assert(dY == mY);
    }

    width = (width == -1 ? src->w : width);
    height = (height == -1 ? src->h : height);

    Uint32 Amask = mask->format->Amask;
    Uint32 SrcAmask = src->format->Amask;
    Uint32 SrcAshift = src->format->Ashift;
    
    Uint32* srcData = (Uint32*)src->pixels + sY*src->pitch/4 + sX;
    Uint32* maskData = (Uint32*)mask->pixels + mY*mask->pitch/4 + mX;
    int srcPitch = src->pitch/4 - width;
    int maskPitch = mask->pitch/4 - width;

    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            if (*maskData & Amask) {
                // Prep color for putpixelalpha
                Uint32 color = *srcData;
                Uint8 alpha = (color & SrcAmask) >> SrcAshift;
                // Color becomes at 255 alpha, use alpha for blending instead
                _PutPixelAlpha(dest, dX + x, dY + y, color | SrcAmask, alpha * alphaPercent / 100);

                if (clear) *srcData = clearColor;
            }
        
            ++srcData;
            ++maskData;
        }
        srcData += srcPitch;
        maskData += maskPitch;
    }
}

void blit(int sX, int sY, SDL_Surface* src, int dX, int dY, SDL_Surface* dest, int width, int height) { start_func
    assert(src);
    assert(dest);

    SDL_Rect sourceRect;
    sourceRect.x = sX;
    sourceRect.y = sY;
    sourceRect.w = (width == -1 ? src->w : width);
    sourceRect.h = (height == -1 ? src->h : height);
    
    SDL_Rect destRect;
    destRect.x = dX;
    destRect.y = dY;
    
    if (SDL_BlitSurface(src, &sourceRect, dest, &destRect)) {
        fatalCrash(0, "SDL Blit error: %s", SDL_GetError());
    }
}

void preGLScreenFlip() { start_func
    if (requestedGL) {
        // @TODO: these should be moved to game loop or removed entirely
        // (including a bk color setting)
        glClear(GL_COLOR_BUFFER_BIT);
        glLoadIdentity();
        glScreenState = 1;
    }
}

void postGLScreenFlip() { start_func
    if ((glScreenState >= 1) && (requestedGL)) {
        SDL_GL_SwapBuffers();
        glScreenState = 0;
    }
}

void setScreenGLAlpha(int alpha) { start_func
    if (alpha < 0) alpha = 0;
    if (alpha > 256) alpha = 256;
    glAlpha = (GLfloat)alpha / 256.0f;
}

void screenFlip(const Rect* area) { start_func
    if (requestedGL) {
        int doFlip = !glScreenState;
        if (glScreenState != 1) preGLScreenFlip();
        // Just blit everything to openGL
        glLoadIdentity();
        if ((area) && (area->w)) {
            textureScreen->store(1, proxyScreen, area->x, area->y, area->x, area->y, area->w, area->h);
            textureScreen->updateTexture();
        }
        glColor4f(1.0f, 1.0f, 1.0f, glAlpha);
        textureScreen->draw(1, 0, 0);
        // Add mouse
        if ((SDL_GetAppState() & SDL_APPMOUSEFOCUS) && (micePointers[currentMouse]))
            micePointers[currentMouse]->textureAt(lastMouseX, lastMouseY);

        if (doFlip) postGLScreenFlip();
        else glScreenState = 2;
    }
    else {
        if (area) {
            if (area->w) {
                updateRects[0].x = area->x;
                updateRects[0].y = area->y;
                updateRects[0].w = area->w;
                updateRects[0].h = area->h;
                SDL_UpdateRects(actualScreen, numUpdate, updateRects);
            }
            else if (numUpdate > 1) {
                SDL_UpdateRects(actualScreen, numUpdate - 1, updateRects + 1);
            }
        }
        else {
            SDL_UpdateRect(actualScreen, 0, 0, 0, 0);
        }
    }
    numUpdate = 1;
}

void prepareFont() { start_func
    if (TTF_Init()) {
        fatalCrash(0, "Unable to initialize TTF library");
    }

    string fontFile;

    for (int pos = 0; pos < FONT_COUNT; ++pos) {
        createFilename(resourceDir->c_str(), fonts[pos].file, fontFile);
        fonts[pos].font = TTF_OpenFont(fontFile.c_str(), fonts[pos].size);
        if (fonts[pos].font == NULL) {
            fatalCrash(0, "Unable to open font %s: %s", fontFile.c_str(), SDL_GetError());
        }
        int junk;
        if (TTF_SizeText(fonts[pos].font, "j", &junk, &fonts[pos].height)) {
            fatalCrash(0, "Unable to size font %s: %s", fontFile.c_str(), SDL_GetError());
        }
        fonts[pos].ascent = TTF_FontAscent(fonts[pos].font);
    }
}

void closeFont() { start_func
    for (int pos = 0; pos < FONT_COUNT; ++pos) {
        if (fonts[pos].font) {
            TTF_CloseFont(fonts[pos].font);
            fonts[pos].font = NULL;
        }
    }
    TTF_Quit();
}

SDL_Surface* createText(const string& text, SDL_Color fg, int alpha, int font) { start_func
    if (text.length() == 0) return NULL;
    assert(font >= 0);
    assert(font < FONT_COUNT);
    assert(fonts[font].font);

    SDL_Surface* textS;
    if (alpha) {
        textS = TTF_RenderText_Blended(fonts[font].font, text.c_str(), fg);
    }
    else {
        textS = TTF_RenderText_Solid(fonts[font].font, text.c_str(), fg);
    }
    if (textS == NULL) {
        fatalCrash(0, "SDL Text rendering error: %s", SDL_GetError());
    }
    return textS;
}

int drawText(const string& text, SDL_Color fg, int x, int y, SDL_Surface* surface, int font) { start_func
    if (text.length() == 0) return 0;

    // Change 1 to 0 to remove alpha
    SDL_Surface* textSurface = createText(text, fg, 1, font);
    assert(textSurface);

    blit(0, 0, textSurface, x, y, surface);
    
    int width = textSurface->w;
    SDL_FreeSurface(textSurface);
    return width;
}

void drawBox(int x, int y, int w, int h, Uint32 color, SDL_Surface* surface) { start_func
    assert(w > 0);
    assert(h > 0);
    assert(surface);
    
    drawHLine(x, x + w - 1, y, color, surface);
    drawHLine(x, x + w - 1, y + h - 1, color, surface);
    drawVLine(y, y + h - 1, x, color, surface);
    drawVLine(y, y + h - 1, x + w - 1, color, surface);
}

int SDL_FillRect(SDL_Surface *dst, Rect *dstrect, Uint32 color) { start_func
    assert(dstrect);
    SDL_Rect newrect = { dstrect->x, dstrect->y, dstrect->w, dstrect->h };
    int ret = SDL_FillRect(dst, &newrect, color);
    dstrect->x = newrect.x;
    dstrect->y = newrect.y;
    dstrect->w = newrect.w;
    dstrect->h = newrect.h;
    return ret;
}

void SDL_SetClipRect(SDL_Surface *surface, Rect *rect) { start_func
    assert(rect);
    SDL_Rect newrect = { rect->x, rect->y, rect->w, rect->h };
    SDL_SetClipRect(surface, &newrect);
    rect->x = newrect.x;
    rect->y = newrect.y;
    rect->w = newrect.w;
    rect->h = newrect.h;
}

void SDL_SetClipRect(SDL_Surface *surface, const Rect *rect) { start_func
    assert(rect);
    SDL_Rect newrect = { rect->x, rect->y, rect->w, rect->h };
    SDL_SetClipRect(surface, &newrect);
}

void drawRect(int x, int y, int w, int h, Uint32 color, SDL_Surface* surface) { start_func
    assert(w > 0);
    assert(h > 0);

    SDL_Rect rect = { x, y, w, h };
    assert(surface);
    // fillrect only fails from locking or <8 bpp surfaces, neither of which we care about
    SDL_FillRect(surface, &rect, color);
}

void drawGradient(int x, int y, int w, int h, SDL_Color color1, SDL_Color color2, SDL_Surface* surface) { start_func
    assert(w > 0);
    assert(h > 0);
    assert(surface);

    SDL_Rect clip;
    SDL_GetClipRect(surface, &clip);
    
    // y2, y clip
    int y2 = y + h - 1;
    if (y2 >= clip.y + clip.h) y2 = clip.y + clip.h - 1;
    if (y < clip.y) y = clip.y;
    if (y2 < y) return;

    // Position gradient is at and max position of gradient
    int pos = 0;
    int maxpos = w - 1;
    // (special case)
    if (maxpos == 0) maxpos = 1;
    
    // x2, x clip (x2 is actually 1 higher than last)
    int x2 = x + w;
    if (x2 > clip.x + clip.w) x2 = clip.x + clip.w;
    if (x < clip.x) {
        // Adjust gradient position too
        pos = clip.x - x;
        x = clip.x;
    }
    if (x2 < x) return;
    
    SDL_Color color;
    for (; x < x2; ++x, ++pos) {
        color.r = color1.r + (color2.r - color1.r) * pos / maxpos;
        color.g = color1.g + (color2.g - color1.g) * pos / maxpos;
        color.b = color1.b + (color2.b - color1.b) * pos / maxpos;
        drawVLine(y, y2, x, convertRGB(color), surface);
    }
}

void drawSelectRect(int x, int y, int w, int h, Uint32 color, SDL_Surface* surface, int alpha) { start_func
    assert(w > 0);
    assert(h > 0);
    assert(surface);

    SDL_Rect rect = { 0, 0, w, h };

    SDL_Surface* selection = createSurface(w, h);
    SDL_SetAlpha(selection, SDL_SRCALPHA, alpha);
    
    // fillrect only fails from locking or <8 bpp surfaces, neither of which we care about
    SDL_FillRect(selection, &rect, color);

    SDL_Rect destRect = { x, y, w, h };

    if (SDL_BlitSurface(selection, &rect, surface, &destRect)) {
        SDL_FreeSurface(selection);
        fatalCrash(0, "SDL Rect error: %s", SDL_GetError());
    }

    SDL_FreeSurface(selection);
}

// (unused)
/*
void drawSelectGradient(int x, int y, int w, int h, SDL_Color color1, SDL_Color color2, SDL_Surface* surface, int alpha) { start_func
    assert(w > 0);
    assert(h > 0);
    assert(surface);

    SDL_Surface* selection = createSurface(w, h);
    SDL_SetAlpha(selection, SDL_SRCALPHA, alpha);
    drawGradient(0, 0, w, h, color1, color2, selection);

    SDL_Rect rect = { 0, 0, w, h };
    SDL_Rect destRect = { x, y, w, h };

    if (SDL_BlitSurface(selection, &rect, surface, &destRect)) {
        SDL_FreeSurface(selection);
        fatalCrash(0, "SDL Rect error: %s", SDL_GetError());
    }

    SDL_FreeSurface(selection);
}
*/

void drawFocusBox(int x, int y, int w, int h, SDL_Surface* surface) { start_func
    assert(w > 0);
    assert(h > 0);

    assert(surface);
    drawDottedHLine(x, x + w - 1, y, guiPacked[COLOR_DARK1], surface);
    drawDottedVLine(y, y + h - 1, x + w - 1, guiPacked[COLOR_DARK1], surface);
    drawDottedHLine(x, x + w - 1, y + h - 1, guiPacked[COLOR_DARK1], surface);
    drawDottedVLine(y, y + h - 1, x, guiPacked[COLOR_DARK1], surface);
}

void drawGuiBox(int x, int y, int w, int h, int thickness, SDL_Surface* surface) { start_func
    assert((thickness == 1) || (thickness == 2));
    int colorl = (thickness == 1) ? COLOR_LIGHT1 : COLOR_LIGHT2;
    int colord = (thickness == 1) ? COLOR_DARK1 : COLOR_DARK2;

    drawRect(x, y, w, h, guiPacked[COLOR_FILL], surface);
    drawHLine(x, x + w - 2, y, guiPacked[colorl], surface);
    drawVLine(y, y + h - 2, x, guiPacked[colorl], surface);
    drawVLine(y + 1, y + h - 1, x + w - 1, guiPacked[colord], surface);
    drawHLine(x + 1, x + w - 1, y + h - 1, guiPacked[colord], surface);
    if (thickness > 1) {
        drawHLine(x + 1, x + w - 3, y + 1, guiPacked[COLOR_LIGHT1], surface);
        drawVLine(y + 1, y + h - 3, x + 1, guiPacked[COLOR_LIGHT1], surface);
        drawVLine(y + 2, y + h - 2, x + w - 2, guiPacked[COLOR_DARK1], surface);
        drawHLine(x + 2, x + w - 2, y + h - 2, guiPacked[COLOR_DARK1], surface);
    }
}

void drawGuiBoxInvert(int x, int y, int w, int h, int thickness, SDL_Surface* surface) { start_func
    assert((thickness == 1) || (thickness == 2));
    
    drawHLine(x, x + w - 2, y, guiPacked[COLOR_DARK1], surface);
    drawVLine(y, y + h - 2, x, guiPacked[COLOR_DARK1], surface);
    drawVLine(y + 1, y + h - 1, x + w - 1, guiPacked[COLOR_LIGHT1], surface);
    drawHLine(x + 1, x + w - 1, y + h - 1, guiPacked[COLOR_LIGHT1], surface);
    if (thickness > 1) {
        drawHLine(x + 1, x + w - 3, y + 1, guiPacked[COLOR_DARK2], surface);
        drawVLine(y + 1, y + h - 3, x + 1, guiPacked[COLOR_DARK2], surface);
        drawVLine(y + 2, y + h - 2, x + w - 2, guiPacked[COLOR_LIGHT2], surface);
        drawHLine(x + 2, x + w - 2, y + h - 2, guiPacked[COLOR_LIGHT2], surface);
    }
}

void drawLine(int x1, int y1, int x2, int y2, Uint32 color, SDL_Surface* surface) { start_func
    Uint16* bufp16;
    Uint32* bufp32;
    assert(surface);

    Sint16 x = x1;
    Sint16 y = y1;
    Sint16 dy = y2 - y1;
    Sint16 dx = x2 - x1;
  
    Sint16 G, DeltaG1, DeltaG2, minG, maxG;
    Sint16 inc = 1;

    SDL_Rect clip;
    SDL_GetClipRect(surface, &clip);
    
    Sint16 cx2 = clip.x + clip.w;
    Sint16 cy2 = clip.y + clip.h;
    
    // Skip entirely?
    if (((x1 < clip.x) && (x2 < clip.x)) ||
        ((y1 < clip.y) && (y2 < clip.y)) ||
        ((x1 >= cx2) && (x2 >= cx2)) ||
        ((y1 >= cy2) && (y2 >= cy2))) return;
  
    if (abs(dy) < abs(dx)) {
        if (dx < 0) {
            dx = -dx;
            dy = -dy;
    
            y1 = y2;
            y2 = y;
            y = y1;
  
            x1 = x2;
            x2 = x;
            x = x1;
        }
        if (dy < 0) {
            dy = -dy;
            inc = -1;
        }
  
        G = 2 * dy - dx;
        DeltaG1 = 2 * (dy - dx);
        DeltaG2 = 2 * dy;
  
        switch (surface->format->BytesPerPixel) {
            case 2:
                bufp16 = (Uint16*)surface->pixels + y*surface->pitch/2 + x;
                if ((x >= clip.x) && (y >= clip.y) && (x < cx2) && (y < cy2)) *bufp16 = color;

                while (++x <= x2) {
                    ++bufp16;
                    if (G > 0) { G += DeltaG1; y += inc; bufp16 += inc*surface->pitch/2; }
                    else G += DeltaG2;
          
                    if ((x >= clip.x) && (y >= clip.y) && (x < cx2) && (y < cy2)) *bufp16 = color;
                }  
                break;
            case 4:
                bufp32 = (Uint32*)surface->pixels + y*surface->pitch/4 + x;
                if ((x >= clip.x) && (y >= clip.y) && (x < cx2) && (y < cy2)) *bufp32 = color;

                while (++x <= x2) {
                    ++bufp32;
                    if (G > 0) { G += DeltaG1; y += inc; bufp32 += inc*surface->pitch/4; }
                    else G += DeltaG2;
          
                    if ((x >= clip.x) && (y >= clip.y) && (x < cx2) && (y < cy2)) *bufp32 = color;
                }  
                break;
        }
    }
    else {
        if (dy < 0) {
            dx = -dx;
            dy = -dy;
    
            y1 = y2;
            y2 = y;
            y = y1;
  
            x1 = x2;
            x2 = x;
            x = x1;
        }
        if (dx < 0) {
            dx = -dx;
            inc = -1;
        }
  
        G = 2 * dx - dy;
        minG = maxG = G;
        DeltaG1 = 2 * (dx - dy);
        DeltaG2 = 2 * dx;
  
        switch (surface->format->BytesPerPixel) {
            case 2:
                bufp16 = (Uint16*)surface->pixels + y*surface->pitch/2 + x;
                if ((x >= clip.x) && (y >= clip.y) && (x < cx2) && (y < cy2)) *bufp16 = color;

                while (++y <= y2) {
                    bufp16 += surface->pitch/2;
                    if (G > 0) { G += DeltaG1; x += inc; bufp16 += inc; }
                    else G += DeltaG2;
          
                    if ((x >= clip.x) && (y >= clip.y) && (x < cx2) && (y < cy2)) *bufp16 = color;
                }  
                break;
            case 4:
                bufp32 = (Uint32*)surface->pixels + y*surface->pitch/4 + x;
                if ((x >= clip.x) && (y >= clip.y) && (x < cx2) && (y < cy2)) *bufp32 = color;

                while (++y <= y2) {
                    bufp32 += surface->pitch/4;
                    if (G > 0) { G += DeltaG1; x += inc; bufp32 += inc; }
                    else G += DeltaG2;
          
                    if ((x >= clip.x) && (y >= clip.y) && (x < cx2) && (y < cy2)) *bufp32 = color;
                }  
                break;
        }
    }
}

void drawHLine(int x1, int x2, int y1, Uint32 color, SDL_Surface* surface) { start_func
    Uint16* bufp16;
    Uint32* bufp32;
    assert(surface);

    Sint16 x = x1;
    Sint16 y = y1;
    
    if (x1 > x2) {
        x1 = x2;
        x2 = x;
        x = x1;
    }
  
    SDL_Rect clip;
    SDL_GetClipRect(surface, &clip);
    
    Sint16 cx2 = clip.x + clip.w;
    Sint16 cy2 = clip.y + clip.h;
    
    // Skip entirely?
    if ((y < clip.y) || (y >= cy2) ||
        (x2 < clip.x) || (x >= cx2)) return;
  
    // Clip
    if (x < clip.x) x = clip.x;
    if (x2 >= cx2) x2 = cx2 - 1;
    
    switch (surface->format->BytesPerPixel) {
        case 2:
            bufp16 = (Uint16*)surface->pixels + y*surface->pitch/2 + x;
            *bufp16 = color;

            while (++x <= x2) {
                *++bufp16 = color;
            }  
            break;
        case 4:
            bufp32 = (Uint32*)surface->pixels + y*surface->pitch/4 + x;
            *bufp32 = color;

            while (++x <= x2) {
                *++bufp32 = color;
            }  
            break;
    }
}

void drawVLine(int y1, int y2, int x1, Uint32 color, SDL_Surface* surface) { start_func
    Uint16* bufp16;
    Uint32* bufp32;
    assert(surface);

    Sint16 x = x1;
    Sint16 y = y1;
    
    if (y1 > y2) {
        y1 = y2;
        y2 = y;
        y = y1;
    }
  
    SDL_Rect clip;
    SDL_GetClipRect(surface, &clip);
    
    Sint16 cx2 = clip.x + clip.w;
    Sint16 cy2 = clip.y + clip.h;
    
    // Skip entirely?
    if ((x < clip.x) || (x >= cx2) ||
        (y2 < clip.y) || (y >= cy2)) return;
  
    // Clip
    if (y < clip.y) y = clip.y;
    if (y2 >= cy2) y2 = cy2 - 1;

    switch (surface->format->BytesPerPixel) {
        case 2:
            bufp16 = (Uint16*)surface->pixels + y*surface->pitch/2 + x;
            *bufp16 = color;

            while (++y <= y2) {
                *(bufp16+= surface->pitch/2) = color;
            }  
            break;
        case 4:
            bufp32 = (Uint32*)surface->pixels + y*surface->pitch/4 + x;
            *bufp32 = color;

            while (++y <= y2) {
                *(bufp32+= surface->pitch/4) = color;
            }  
            break;
    }
}

void drawDottedHLine(int x1, int x2, int y1, Uint32 color, SDL_Surface* surface) { start_func
    Uint16* bufp16;
    Uint32* bufp32;
    assert(surface);

    Sint16 x = x1;
    Sint16 y = y1;
    
    if (x1 > x2) {
        x1 = x2;
        x2 = x;
        x = x1;
    }
  
    SDL_Rect clip;
    SDL_GetClipRect(surface, &clip);
    
    Sint16 cx2 = clip.x + clip.w;
    Sint16 cy2 = clip.y + clip.h;
    
    // Skip entirely?
    if ((y < clip.y) || (y >= cy2) ||
        (x2 < clip.x) || (x >= cx2)) return;
  
    // Clip
    if (x < clip.x) x = clip.x;
    if (x2 >= cx2) x2 = cx2 - 1;

    Sint16 dot = (x + y) & 1;
    
    switch (surface->format->BytesPerPixel) {
        case 2:
            bufp16 = (Uint16*)surface->pixels + y*surface->pitch/2 + x;
            if (dot) *bufp16 = color;

            while (++x <= x2) {
                ++bufp16;
                dot = dot ^ 1;
                if (dot) *bufp16 = color;
            }  
            break;
        case 4:
            bufp32 = (Uint32*)surface->pixels + y*surface->pitch/4 + x;
            if (dot) *bufp32 = color;

            while (++x <= x2) {
                ++bufp32;
                dot = dot ^ 1;
                if (dot) *bufp32 = color;
            }  
            break;
    }
}

void drawDottedVLine(int y1, int y2, int x1, Uint32 color, SDL_Surface* surface) { start_func
    Uint16* bufp16;
    Uint32* bufp32;
    assert(surface);

    Sint16 x = x1;
    Sint16 y = y1;
    
    if (y1 > y2) {
        y1 = y2;
        y2 = y;
        y = y1;
    }
  
    SDL_Rect clip;
    SDL_GetClipRect(surface, &clip);
    
    Sint16 cx2 = clip.x + clip.w;
    Sint16 cy2 = clip.y + clip.h;
    
    // Skip entirely?
    if ((x < clip.x) || (x >= cx2) ||
        (y2 < clip.y) || (y >= cy2)) return;
  
    // Clip
    if (y < clip.y) y = clip.y;
    if (y2 >= cy2) y2 = cy2 - 1;
    
    Sint16 dot = (x + y) & 1;
    
    switch (surface->format->BytesPerPixel) {
        case 2:
            bufp16 = (Uint16*)surface->pixels + y*surface->pitch/2 + x;
            if (dot) *bufp16 = color;

            while (++y <= y2) {
                bufp16+= surface->pitch/2;
                dot = dot ^ 1;
                if (dot) *bufp16 = color;
            }  
            break;
        case 4:
            bufp32 = (Uint32*)surface->pixels + y*surface->pitch/4 + x;
            if (dot) *bufp32 = color;

            while (++y <= y2) {
                bufp32 += surface->pitch/4;
                dot = dot ^ 1;
                if (dot) *bufp32 = color;
            }  
            break;
    }
}

void drawAntsBox(int x1, int y1, int w, int h, int phase, SDL_Surface* surface) { start_func
    assert(surface);
    
    drawAntsHLine(x1, x1 + w - 1, y1, phase, surface);
    drawAntsHLine(x1, x1 + w - 1, y1 + h - 1, phase, surface);
    drawAntsVLine(y1, y1 + h - 1, x1, phase, surface);
    drawAntsVLine(y1, y1 + h - 1, x1 + w - 1, phase, surface);
}

void drawAntsHLine(int x1, int x2, int y1, int phase, SDL_Surface* surface) { start_func
    Uint16* bufp16;
    Uint32* bufp32;
    assert(surface);
    
    Sint16 x = x1;
    Sint16 y = y1;
    
    if (x1 > x2) swap(x1, x2);

    SDL_Rect clip;
    SDL_GetClipRect(surface, &clip);
    
    Sint16 cx2 = clip.x + clip.w;
    Sint16 cy2 = clip.y + clip.h;
    
    // Skip entirely?
    if ((y < clip.y) || (y >= cy2) ||
        (x2 < clip.x) || (x >= cx2)) return;
  
    // Clip
    if (x < clip.x) x = clip.x;
    if (x2 >= cx2) x2 = cx2 - 1;

    phase = (phase + x + y) % NUM_PHASES;
    
    switch (surface->format->BytesPerPixel) {
        case 2:
            bufp16 = (Uint16*)surface->pixels + y*surface->pitch/2 + x;
            *bufp16 = phaseColors[phase];
            phase = (phase + 1) % NUM_PHASES;

            while (++x <= x2) {
                *++bufp16 = phaseColors[phase];
                phase = (phase + 1) % NUM_PHASES;
            }  
            break;
        case 4:
            bufp32 = (Uint32*)surface->pixels + y*surface->pitch/4 + x;
            *bufp32 = phaseColors[phase];
            phase = (phase + 1) % NUM_PHASES;

            while (++x <= x2) {
                *++bufp32 = phaseColors[phase];
                phase = (phase + 1) % NUM_PHASES;
            }  
            break;
    }
}

void drawAntsVLine(int y1, int y2, int x1, int phase, SDL_Surface* surface) { start_func
    Uint16* bufp16;
    Uint32* bufp32;
    assert(surface);

    Sint16 x = x1;
    Sint16 y = y1;
    
    if (y1 > y2) swap(y1, y2);

    SDL_Rect clip;
    SDL_GetClipRect(surface, &clip);
    
    Sint16 cx2 = clip.x + clip.w;
    Sint16 cy2 = clip.y + clip.h;
    
    // Skip entirely?
    if ((x < clip.x) || (x >= cx2) ||
        (y2 < clip.y) || (y >= cy2)) return;
  
    // Clip
    if (y < clip.y) y = clip.y;
    if (y2 >= cy2) y2 = cy2 - 1;
    
    phase = (phase + x + y) % NUM_PHASES;

    switch (surface->format->BytesPerPixel) {
        case 2:
            bufp16 = (Uint16*)surface->pixels + y*surface->pitch/2 + x;
            *bufp16 = phaseColors[phase];
            phase = (phase + 1) % NUM_PHASES;

            while (++y <= y2) {
                *(bufp16+= surface->pitch/2) = phaseColors[phase];
                phase = (phase + 1) % NUM_PHASES;
            }  
            break;
        case 4:
            bufp32 = (Uint32*)surface->pixels + y*surface->pitch/4 + x;
            *bufp32 = phaseColors[phase];
            phase = (phase + 1) % NUM_PHASES;

            while (++y <= y2) {
                *(bufp32+= surface->pitch/4) = phaseColors[phase];
                phase = (phase + 1) % NUM_PHASES;
            }  
            break;
    }
}

int fontWidth(const string& text, int font) { start_func
    int width;
    int junk;
    if (text.length() == 0) return 0;

    assert(font >= 0);
    assert(font < FONT_COUNT);
    assert(fonts[font].font);

    if (TTF_SizeText(fonts[font].font, text.c_str(), &width, &junk)) {
        fatalCrash(0, "SDL Error sizing text: %s", SDL_GetError());
    }
    return width;
}

int convertGuiText(string* text, char* shortcut, int font) { start_func
    assert(font >= 0);
    assert(font < FONT_COUNT);
    assert(fonts[font].font);
    assert(text);

    *shortcut = 0;
    if (text->length() == 0) return 0;

    // Contains a \t?
    int pos = text->find('\t', 0);
    if (pos < 0) return 0;
    
    // Remove from string
    text->erase(pos, 1);
    
    // Calculate start of underline
    int start = 0;
    int junk;
    if (pos > 0) {
        string sub(*text, 0, pos);
        if (TTF_SizeText(fonts[font].font, sub.c_str(), &start, &junk)) {
            fatalCrash(0, "SDL Error sizing text: %s", SDL_GetError());
        }
    }
    
    // Calculate size of underline
    int size = 0;
    string sub(*text, pos, 1);
    // (no character after \t? no underline)
    if (sub.length() == 0) return 0;
    if (TTF_SizeText(fonts[font].font, sub.c_str(), &size, &junk)) {
        fatalCrash(0, "SDL Error sizing text: %s", SDL_GetError());
    }
    
    // Grab shortcut key
    if (shortcut) {
        *shortcut = tolower(sub[0]);
    }
    
    // Generate code (max 255 on start and size)
    return (start & 0xFF) | ((size & 0xFF) << 8);
}

void drawTextUnderline(int underlineCode, Uint32 color, int x, int y, SDL_Surface* surface, int font) { start_func
    assert(underlineCode >= 0);
    assert(underlineCode <= 0xFFFF);
    assert(font >= 0);
    assert(font < FONT_COUNT);
    assert(fonts[font].font);
    assert(surface);

    int start = underlineCode & 0xFF;
    int size = underlineCode >> 8;

    if (size) drawHLine(x + start - 1, x + start + size - 1, y + fonts[font].ascent + 1, color, surface);
}

void drawCheckbox(int isRadio, int isChecked, int isDisabled, int x, int y, int size, SDL_Surface* surface) { start_func
    assert(surface);

    if (isRadio) {
        int rad = (size + 1) / 2;
        int tweak = (size & 1) ^ 1;
        --rad;
        sge_FilledCircleDual(surface, x + rad, y + rad, rad, tweak, guiPacked[COLOR_DARK1], guiPacked[COLOR_LIGHT1]);
        sge_FilledCircleDual(surface, x + rad, y + rad, rad - 1, tweak, guiPacked[COLOR_DARK2], guiPacked[COLOR_LIGHT2]);
        sge_FilledCircleDual(surface, x + rad, y + rad, rad - 2, tweak, guiPacked[isDisabled ? COLOR_FILL : COLOR_TEXTBOX], guiPacked[isDisabled ? COLOR_FILL : COLOR_TEXTBOX]);
        if (isChecked) {
            sge_FilledCircleDual(surface, x + rad, y + rad, rad - 3, tweak, guiPacked[COLOR_DARK1], guiPacked[COLOR_DARK1]);
            sge_FilledCircleDual(surface, x + rad, y + rad, rad - 4, tweak, guiPacked[isDisabled ? COLOR_DARK1 : COLOR_TEXT], guiPacked[isDisabled ? COLOR_DARK1 : COLOR_TEXT]);
        }
    }
    else {
        drawRect(x, y, size, size, guiPacked[COLOR_FILL], surface);
        drawGuiBoxInvert(x, y, size, size, 2, surface);
        if (!isDisabled) drawRect(x + 2, y + 2, size - 4, size - 4, guiPacked[COLOR_TEXTBOX], surface);
        if (isChecked) {
            sge_Line(surface, x + 4, y + 3, x + size - 4, y + size - 5, guiPacked[COLOR_DARK2]);
            sge_Line(surface, x + 3, y + 3, x + size - 4, y + size - 4, guiPacked[isDisabled ? COLOR_DARK1 : COLOR_TEXT]);
            sge_Line(surface, x + 3, y + 4, x + size - 5, y + size - 4, guiPacked[COLOR_DARK2]);
            sge_Line(surface, x + 4, y + size - 4, x + size - 4, y + 4, guiPacked[COLOR_DARK2]);
            sge_Line(surface, x + 3, y + size - 4, x + size - 4, y + 3, guiPacked[isDisabled ? COLOR_DARK1 : COLOR_TEXT]);
            sge_Line(surface, x + 3, y + size - 5, x + size - 5, y + 3, guiPacked[COLOR_DARK2]);
        }
    }
}

int fontHeight(int font) { start_func
    assert(font >= 0);
    assert(font < FONT_COUNT);
    assert(fonts[font].font);

    return fonts[font].height;
}

int fontAscent(int font) { start_func
    assert(font >= 0);
    assert(font < FONT_COUNT);
    assert(fonts[font].font);

    return fonts[font].ascent;
}

SDL_Surface* createSurface(int width, int height) { start_func
    SDL_Surface* ptr = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, proxyScreen->format->BitsPerPixel,
        proxyScreen->format->Rmask, proxyScreen->format->Gmask, proxyScreen->format->Bmask, proxyScreen->format->Amask);
        
    if (ptr == NULL) fatalCrash(0, "SDL Error allocating surface %d by %d", width, height);

    SDL_SetAlpha(ptr, 0, 255);
    return ptr;
}

SDL_Surface* createSurface32(int width, int height) { start_func
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
    Uint32 rMask = 0xff000000;
    Uint32 gMask = 0x00ff0000;
    Uint32 bMask = 0x0000ff00;
    Uint32 aMask = 0x000000ff;
#else
    Uint32 rMask = 0x000000ff;
    Uint32 gMask = 0x0000ff00;
    Uint32 bMask = 0x00ff0000;
    Uint32 aMask = 0xff000000;
#endif

    SDL_Surface* ptr = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 32, rMask, gMask, bMask, aMask);

    if (ptr == NULL) fatalCrash(0, "SDL Error allocating surface %d by %d by 8", width, height);

    SDL_SetAlpha(ptr, 0, 255);
    return ptr;
}

SDL_Surface* createSurface8(int width, int height) { start_func
    SDL_Surface* ptr = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 8, 0, 0, 0, 0);

    if (ptr == NULL) fatalCrash(0, "SDL Error allocating surface %d by %d by 32", width, height);

    return ptr;
}

SDL_Surface* getScreen() { start_func
    assert(proxyScreen);
    return proxyScreen;
}

int guiOk() { start_func
    return proxyScreen ? 1 : 0;
}

void selectMouse(int type) { start_func
    assert(type >= 0);
    assert(type < MOUSE_COUNT);

    currentMouse = type;
}

int mouseTooltipYOffset() { start_func
    return micePointers[currentMouse]->tooltipYOffset();
}

void blitMouse() { start_func
    if ((SDL_GetAppState() & SDL_APPMOUSEFOCUS) && (micePointers[currentMouse])) {
        SDL_GetMouseState(&lastMouseX, &lastMouseY);
        if (!requestedGL) {
            if (numUpdate == 1) {
                micePointers[currentMouse]->displayAt(lastMouseX, lastMouseY, mouseUnblit, updateRects + 1);
                numUpdate = 2;
            }
            else {
                micePointers[currentMouse]->displayAt(lastMouseX, lastMouseY, mouseUnblit, updateRects + 2);
                numUpdate = 3;
            }
            mouseBlitted = currentMouse;
        }
    }
}

void unblitMouse() { start_func
    if ((mouseBlitted >= 0) && (micePointers[mouseBlitted]) && (!requestedGL)) {
        micePointers[mouseBlitted]->unDisplay(lastMouseX, lastMouseY, mouseUnblit, updateRects + 1);
        if (numUpdate < 2) numUpdate = 2;
        mouseBlitted = -1;
    }
}

// Clipping routines
int intersectRects(Rect& a, const Rect& b) { start_func
    if (a.x < b.x) {
        Sint32 aw = a.w + a.x - b.x;
        if (aw <= 0) {
            a.w = 0;
            return 0;
        }
        a.x = b.x;
        if (aw > b.w) aw = b.w;
        a.w = aw;
    }
    else {
        Sint32 bw = b.w + b.x - a.x;
        if (bw <= 0) {
            a.w = 0;
            return 0;
        }
        if (a.w > bw) a.w = bw;
    }
    if (a.y < b.y) {
        Sint32 ah = a.h + a.y - b.y;
        if (ah <= 0) {
            a.w = 0;
            return 0;
        }
        a.y = b.y;
        if (ah > b.h) ah = b.h;
        a.h = ah;
    }
    else {
        Sint32 bh = b.h + b.y - a.y;
        if (bh <= 0) {
            a.w = 0;
            return 0;
        }
        if (a.h > bh) a.h = bh;
    }
    return a.w;
}

int boundRects(Rect& a, const Rect& b) { start_func
    // (special case)
    if (b.w == 0) {
        return a.w;
    }
    if (a.w == 0) {
        a = b;
        return 1;
    }
    if (a.x > b.x) {
        a.w += a.x - b.x;
        a.x = b.x;
        if (a.w < b.w) a.w = b.w;
    }
    else {
        Sint32 bw = b.w + b.x - a.x;
        if (a.w < bw) a.w = bw;
    }
    if (a.y > b.y) {
        a.h += a.y - b.y;
        a.y = b.y;
        if (a.h < b.h) a.h = b.h;
    }
    else {
        Sint32 bh = b.h + b.y - a.y;
        if (a.h < bh) a.h = bh;
    }
    return 1;
}

Rect createRect(int x1, int y1, int x2, int y2) { start_func
    Rect data;
    
    if (x1 > x2) swap(x1, x2);
    if (y1 > y2) swap(y1, y2);
    
    data.x = x1;
    data.y = y1;
    data.w = x2 - x1 + 1;
    data.h = y2 - y1 + 1;
    
    return data;
}

// Helper function for flood fill
inline int colorDifference(Uint32 a, Uint32 b) { start_func
    // Special optimized case
    if (a == b) return 0;
    
    // Zero alpha matches itself and itself only, regardless of tolerance
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
    if (((a & 0x000000ff) == 0) || ((b & 0x000000ff) == 0)) {
        if ((a & 0x000000ff) == (b & 0x000000ff)) return 0;
        // (arbitrarily large value)
        return 0x7FFF;
    }
#else
    if (((a & 0xff000000) == 0) || ((b & 0xff000000) == 0)) {
        if ((a & 0xff000000) == (b & 0xff000000)) return 0;
        // (arbitrarily large value)
        return 0x7FFF;
    }
#endif
    
    // Last 8 bits we must shift before subtracting, so negatives are properly discovered
    return
     abs((int)(a & 0x000000FF) - (int)(b & 0x000000FF))        +
    (abs((int)(a & 0x0000FF00) - (int)(b & 0x0000FF00)) >>  8) +
    (abs((int)(a & 0x00FF0000) - (int)(b & 0x00FF0000)) >> 16) +
     abs((int)((a & 0xFF000000) >> 24) - (int)((b & 0xFF000000) >> 24))
    ;
}

// Flood fill- see .h for usage notes
Rect floodFill32(SDL_Surface* src, SDL_Surface* dest, int x, int y, Uint32 color, int tolerance) { start_func
    assert(src);
    assert(dest);
    assert(tolerance >= 0);

    // Min/Max area
    Rect result = { 0, 0, 0, 0 };

    // Clip area
    SDL_Rect Sclip;
    SDL_Rect SclipD;
    SDL_GetClipRect(src, &Sclip);
    SDL_GetClipRect(dest, &SclipD);
    Rect clip = { Sclip.x, Sclip.y, Sclip.w, Sclip.h };
    Rect clipD = { SclipD.x, SclipD.y, SclipD.w, SclipD.h };
    if (!intersectRects(clip, clipD)) return result;
    
    int minX = clip.x;
    int minY = clip.y;
    int maxX = clip.x + clip.w - 1;
    int maxY = clip.y + clip.h - 1;
    
    if ((x < minX) || (x > maxX) || (y < minY) || (y > maxY)) return result;
    
    // Extreme points
    int x1 = x;
    int x2 = x;
    int y1 = y;
    int y2 = y;
    
    // Stack
#define FLOOD_STACK_SIZE 250
    struct {
        Uint32* s;
        Uint32* d;
        int x;
        int y;
        int yD;
    } floodStack[FLOOD_STACK_SIZE];
    int stackPos = 0;
    
#define PUSH(sp, dp, xp, yp, ydiff) {\
    if (stackPos < FLOOD_STACK_SIZE) {\
        floodStack[stackPos].s = sp;\
        floodStack[stackPos].d = dp;\
        floodStack[stackPos].x = xp;\
        floodStack[stackPos].y = yp;\
        floodStack[stackPos++].yD = ydiff;\
    }\
}\

#define POP(sp, dp, xp, yp, ydiff) {\
    assert(stackPos);\
    sp = floodStack[--stackPos].s;\
    dp = floodStack[stackPos].d;\
    xp = floodStack[stackPos].x;\
    yp = floodStack[stackPos].y;\
    ydiff = floodStack[stackPos].yD;\
}\
    
    // Starting point and pitch, etc.
    int srcPitch = src->pitch / 4;
    int destPitch = dest->pitch / 4;
    Uint32* srcPoint = (Uint32*)src->pixels + x + y * srcPitch;
    Uint32* destPoint = (Uint32*)dest->pixels + x + y * destPitch;
    Uint32 targetColor = *srcPoint;
    
    // Special case
    if ((src == dest) && (targetColor == color) && (tolerance == 0)) return result;

    // Work areas
    int top, bottom;
    int lX;
    int yDiff;
    
    // Push starting point
    PUSH(srcPoint, destPoint, x, y, 0);
    
    while (stackPos) {
        // Next point to fill, then scan left and right
        POP(srcPoint, destPoint, x, y, yDiff);
        srcPoint += yDiff * srcPitch;
        destPoint += yDiff * destPitch;
        y += yDiff;
        
        // Don't fill given point directly- instead, we'll scan it manually in the second loop
        // Update extreme points- y
        if (y < y1) y1 = y;
        if (y > y2) y2 = y;
        
        // Check both top and bottom for matches
        top = bottom = 1;
        
        // Fill as far left as possible
        lX = x;
        while ((lX > minX) && (colorDifference(srcPoint[-1], targetColor) <= tolerance) && (destPoint[-1] != color)) {
            --lX;
            --srcPoint;
            --destPoint;
            *destPoint = color;
            
            // Is point above fillable?
            if ((y > minY) && (colorDifference(srcPoint[-srcPitch], targetColor) <= tolerance) && (destPoint[-srcPitch] != color)) {
                // Are we pushing "above" points right now?
                if (top) {
                    // Push onto stack
                    PUSH(srcPoint, destPoint, lX, y, -1);
                    top = 0;
                }
            }
            // Not fillable, we will now push the next "above" point we find
            else top = 1;
            
            // Is point below fillable?
            if ((y < maxY) && (colorDifference(srcPoint[srcPitch], targetColor) <= tolerance) && (destPoint[srcPitch] != color)) {
                // Are we pushing "below" points right now?
                if (bottom) {
                    // Push onto stack
                    PUSH(srcPoint, destPoint, lX, y, 1);
                    bottom = 0;
                }
            }
            // Not fillable, we will now push the next "below" point we find
            else bottom = 1;
        }
        
        // Update extreme left point
        if (lX < x1) x1 = lX;
        
        // Recenter, minus one, so that we scan the center/given point as well
        --x;
        srcPoint += x - lX;
        destPoint += x - lX;
        top = bottom = 1;
        
        // Fill as far right as possible; this loop intentionally catches our first, central point
        while ((x < maxX) && (colorDifference(srcPoint[1], targetColor) <= tolerance) && (destPoint[1] != color)) {
            ++x;
            ++srcPoint;
            ++destPoint;
            *destPoint = color;
            
            // Is point above fillable?
            if ((y > minY) && (colorDifference(srcPoint[-srcPitch], targetColor) <= tolerance) && (destPoint[-srcPitch] != color)) {
                // Are we pushing "above" points right now?
                if (top) {
                    // Push onto stack
                    PUSH(srcPoint, destPoint, x, y, -1);
                    top = 0;
                }
            }
            // Not fillable, we will now push the next "above" point we find
            else top = 1;
            
            // Is point below fillable?
            if ((y < maxY) && (colorDifference(srcPoint[srcPitch], targetColor) <= tolerance) && (destPoint[srcPitch] != color)) {
                // Are we pushing "below" points right now?
                if (bottom) {
                    // Push onto stack
                    PUSH(srcPoint, destPoint, x, y, 1);
                    bottom = 0;
                }
            }
            // Not fillable, we will now push the next "below" point we find
            else bottom = 1;
        }

        // Update extreme right point
        if (x > x2) x2 = x;
    }
    
    result.x = x1;
    result.y = y1;
    result.w = x2 - x1 + 1;
    result.h = y2 - y1 + 1;
    return result;
}

Rect floodFillNonContiguous32(SDL_Surface* src, SDL_Surface* dest, int x, int y, Uint32 color, int tolerance) { start_func
    assert(src);
    assert(dest);
    assert(tolerance >= 0);

    // Min/Max area
    Rect result = { 0, 0, 0, 0 };
    
    // Clip area
    SDL_Rect Sclip;
    SDL_Rect SclipD;
    SDL_GetClipRect(src, &Sclip);
    SDL_GetClipRect(dest, &SclipD);
    Rect clip = { Sclip.x, Sclip.y, Sclip.w, Sclip.h };
    Rect clipD = { SclipD.x, SclipD.y, SclipD.w, SclipD.h };
    if (!intersectRects(clip, clipD)) return result;
    
    int minX = clip.x;
    int minY = clip.y;
    int maxX = clip.x + clip.w - 1;
    int maxY = clip.y + clip.h - 1;
    
    if ((x < minX) || (x > maxX) || (y < minY) || (y > maxY)) return result;
    
    // Extreme points
    int x1 = x;
    int x2 = x;
    int y1 = y;
    int y2 = y;
    
    // Starting point and pitch, etc.
    int srcPitch = src->pitch / 4;
    int destPitch = dest->pitch / 4;
    Uint32* srcPoint = (Uint32*)src->pixels + minX + minY * srcPitch;
    Uint32* destPoint = (Uint32*)dest->pixels + minX + minY * destPitch;
    Uint32 targetColor = *((Uint32*)src->pixels + x + y * (src->pitch / 4));

    // Special case
    if ((src == dest) && (targetColor == color) && (tolerance == 0)) return result;
    
    // Preadjust pitches
    srcPitch -= maxX - minX + 1;
    destPitch -= maxX - minX + 1;

    for (y = minY; y <= maxY; ++y) {
        for (x = minX; x <= maxX; ++x) {
            if (colorDifference(*srcPoint, targetColor) <= tolerance) {
                *destPoint = color;
                
                // Update extreme points
                if (y < y1) y1 = y;
                if (y > y2) y2 = y;
                if (x < x1) x1 = x;
                if (x > x2) x2 = x;
            }
            
            ++srcPoint;
            ++destPoint;
        }
        
        srcPoint += srcPitch;
        destPoint += destPitch;
    }
    
    result.x = x1;
    result.y = y1;
    result.w = x2 - x1 + 1;
    result.h = y2 - y1 + 1;
    return result;
}

